#pragma once

#include <memory>
#include <assert.h>
#include <gtl/smart_ptr/deleter.h>

namespace gtl
{

namespace shared
{

class shared_count
{
public:
	shared_count() : m_ref(1)
	{
	}

	void inc()
	{
		++m_ref;
		//InterlockedIncrement(&m_ref);
	}

	bool dec()
	{
		return (--m_ref) == 0;
		//return(InterlockedDecrement(&m_ref) == 0);
	}

private:
	shared_count(const shared_count& r);
	shared_count operator=(const shared_count& r);

	long m_ref;
};

} // end of namespace shared

template<class T, class Deleter = deleter::object>
class shared_ptr
{
private:
	typedef shared_ptr<T, Deleter> self_type;

public:
	typedef T element_type;
	typedef T value_type;
	typedef T* pointer;

public:
	shared_ptr() : m_ptr(NULL), m_pn(NULL)
	{
	}

	template<class Y>
	shared_ptr(Y* p) : m_ptr(p)
	{
		m_pn = (p == NULL ? NULL : new shared::shared_count);
	}

	shared_ptr(const self_type& ptr)
	{
		m_ptr = ptr.m_ptr;
		m_pn = ptr.m_pn;
		if(m_pn != NULL)
			m_pn->inc();
	}

	~shared_ptr()
	{
		release();
	}

	shared_ptr& operator=(const self_type& r)
	{
		release();
		m_ptr = r.m_ptr;
		m_pn = r.m_pn;
		if(m_pn != NULL)
			m_pn->inc();
		return *this;
	}

	shared_ptr& operator=(T* p)
	{
		release();
		m_ptr = p;
		m_pn = (p == NULL ? NULL : new shared::shared_count);
		return *this;
	}

	shared_ptr& operator=(int null) throw(...)
	{
		if(null != 0)
			throw -3;

		release();
		m_ptr = 0;
		m_pn = NULL;
		return *this;
	}

	T* detach()
	{
		if(m_pn != NULL)
		{
			delete m_pn;
			m_pn = NULL;
		}

		T* tmp = m_ptr;
		m_ptr = NULL;
		return tmp;
	}

	T* get() const
	{
		return m_ptr;
	}

	void swap(self_type& other)
	{
		std::swap(m_ptr, other.m_ptr);
		std::swap(m_pn, other.m_pn);
	}

	void reset()
	{
		self_type().swap(*this);
	}

	template<class Y> 
	void reset(Y* p)
	{
		if(p == m_ptr)
			return;
		self_type(p).swap(*this);
	}

	T* operator->() const
	{
		assert(m_ptr != NULL);
		return m_ptr;
	}

	T& operator*() const
	{
		assert(m_ptr != NULL);
		return *m_ptr;
	}

	operator T*() const
	{
		return m_ptr;
	}

	operator T*&()
	{
		return m_ptr;
	}

	T** operator&()
	{
		return &m_ptr;
	}

	T** operator&() const
	{
		return &m_ptr;
	}

public:
	void addref()
	{
		if(m_pn != NULL)
			m_pn->inc();
	}

	void release()
	{
		if(m_pn != NULL && m_pn->dec())
		{
			delete m_pn;
			if(m_ptr != NULL)
				Deleter()(m_ptr);
		}
		m_ptr = NULL;
		m_pn = NULL;
	}

private:
	T* m_ptr;
	shared::shared_count* m_pn;
};

} // end of namespace gtl